﻿using System;
using System.ComponentModel;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using VideoReflector.Models;
using VideoReflector.Controllers;
using nVLC.Declarations;
using nVLC.Declarations.Players;
using nVLC.Declarations.Media;
using nVLC.Declarations.Filters;
using nVLC.Declarations.Events;
using nVLC.Implementation;
using nVLC.Implementation.Media;

namespace VideoReflector.Video
{
    /// <summary>
    /// Abstract implementation of IClipStreamer using libvlc to allow streaming of video clips, used as base for
    /// implementations of clip playback and export
    /// </summary>
    public abstract class VlcClipStreamer : NotifyingObject, IClipStreamer
    {
        #region Private fields

        private string _sessionFileDirectory;
        private IMediaPlayerFactory _vlcMediaFactory;
        private IMediaList _vlcMediaList;
        private bool _addTitles;

        #endregion

        #region Protected fields

        protected IMediaListPlayer _vlcListPlayer;
        protected IList<ClipTag> _clipTags;
        protected VideoFileType _sessionVideo;
        protected int _currentClipIndex;

        #endregion

        #region Constructor and initialiser

        /// <summary>
        /// Constructor for VlcClipStreamer object
        /// </summary>
        /// <param name="clipTags"> Collection of ClipTag objects which correspond to the clips to be exported </param>
        /// <param name="sessionVideo"> Object containing details of video file associated with session </param>
        /// <param name="sessionFileDirectory"> Directory where session files are contained in on disk </param>
        /// <param name="addTitles"> Whether or not to add title screens between clips </param>
        protected VlcClipStreamer(IList<ClipTag> clipTags, VideoFileType sessionVideo, string sessionFileDirectory, bool addTitles = true)
        {
            _clipTags = clipTags;
            _sessionVideo = sessionVideo;
            _sessionFileDirectory = sessionFileDirectory;
            _addTitles = addTitles;
        }

        /// <summary>
        /// Intialise VlcClipStreamer object, constructing nVLC (libvlc interop) objects and adding appropriate event
        /// handlers. MUST be called before attempting to use object however separated from constructor to allow use of
        /// virtual methods which can be overriden in derived classes
        /// </summary>
        protected void Initialise()
        {
            // create nVLC objects
            _vlcMediaFactory = new MediaPlayerFactory();
            _vlcMediaList = GenerateMediaList();
            _vlcListPlayer = _vlcMediaFactory.CreateMediaListPlayer<IMediaListPlayer>(_vlcMediaList);
            // send notification on player state change to allow for binding
            _vlcListPlayer.Events.PlayerPlaying += (s, e) => NotifyPropertyChanged("IsPlaying");
            _vlcListPlayer.Events.PlayerStopped += (s, e) => { 
                                                                NotifyPropertyChanged("IsPlaying"); 
                                                                NotifyPropertyChanged("IsStopped"); 
                                                             };
            _vlcListPlayer.Events.PlayerPaused += (s, e) => NotifyPropertyChanged("IsPlaying");
            _vlcListPlayer.Events.MediaEnded += (s, e) => NotifyPropertyChanged("IsPlaying");
            _vlcListPlayer.Events.TimeChanged += (s, e) => NotifyPropertyChanged("Time");
            // update clip number on media change
            _vlcListPlayer.Events.MediaChanged += Events_MediaChanged;
            if (_addTitles)
            {
                // TODO: add marquee text
            }
        }

        #endregion // Constructor and initialiser

        #region Event handlers

        /// <summary>
        /// Update clip number and fire ClipChanged event on MediaChanged event occuring on nVLC player object
        /// </summary>
        /// <param name="sender"> Sending object </param>
        /// <param name="e"> Event arguments </param>
        private void Events_MediaChanged(object sender, MediaPlayerMediaChanged e)
        {
            _currentClipIndex = _vlcMediaList.IndexOf(e.NewMedia);
            NotifyPropertyChanged("ClipNumber");
            if (ClipChanged != null && _currentClipIndex >=0 && _currentClipIndex < _clipTags.Count)
                ClipChanged(this, new PlayingClipChangedEventArgs(_clipTags[_currentClipIndex]));
            if (_addTitles)
            {
                // TODO: change Marquee text
            }
        }

        #endregion // Event handlers

        #region Helpers

        /// <summary>
        /// Generate nVLC MediaList consisting of playback of intervals of session video corresponding to
        /// start and end times of ClipTag objects
        /// </summary>
        private IMediaList GenerateMediaList()
        {
            string videoFilePath = _sessionVideo.Location.pathIsRelative
                    ? System.IO.Path.Combine(_sessionFileDirectory, _sessionVideo.Location.Value) 
                    : _sessionVideo.Location.Value;
            var mediaList = _vlcMediaFactory.CreateMediaList<IMediaList>(new List<string>());

            foreach (var tag in _clipTags)
            {
                var clipMediaItem = _vlcMediaFactory.CreateMedia<IMedia>(videoFilePath, GenerateMediaItemOptions(tag));
                mediaList.Add(clipMediaItem);

            }

            return mediaList;
        }

        /// <summary>
        /// Create an array of VLC option strings corresponding to the required playback parameters for and individual
        /// clip tag. May be overriden by derived classes to allow addition of options specific to their particular
        /// output mode (e.g. to screen, to a file etc.)
        /// </summary>
        /// <param name="tag"> ClipTag object to generate options for </param>
        /// <returns> Array of VLC option strings </returns>
        protected virtual string[] GenerateMediaItemOptions(ClipTag tag)
        {
            return new string[] {
                    ":file-caching=300",
                    // HACK: bug in VLC assumes frame rate of 30FPS before video loaded hence start times need to be adjusted
                    ":start-time=" + tag.StartTime * _sessionVideo.FrameRate / 30,  
                    ":stop-time=" + tag.EndTime
                };
        }

        #endregion // Helpers

        #region IDisposable implementations

        /// <summary>
        /// Dispose of nVLC objects which reference unmanaged resources
        /// </summary>
        public void Dispose()
        {
            Stop();
            _vlcMediaList.Dispose();
            _vlcListPlayer.Dispose();
            _vlcMediaFactory.Dispose();
        }

        #endregion // IDisposable implementations

        #region IClipStreamer implementations

        public void Play()
        {
            _vlcListPlayer.Play();
        }

        public void Stop()
        {
            _vlcListPlayer.Stop();
        }

        public bool IsPlaying
        {
            get { return _vlcListPlayer.IsPlaying; }
        }

        public bool IsStopped
        {
            get { return _vlcListPlayer.PlayerState == MediaState.Stopped; }
        }

        public virtual int ClipNumber
        {
            get
            {
                return _currentClipIndex;
            }
        }

        public event EventHandler<PlayingClipChangedEventArgs> ClipChanged;

        #endregion // IClipStreamer implementations
    }
}
